package com.b511.swpu.cementbackend.service.impl;

import com.b511.swpu.cementbackend.dto.output.*;
import com.b511.swpu.cementbackend.entity.*;
import com.b511.swpu.cementbackend.repository.MetaSystemUnitRepository;
import com.b511.swpu.cementbackend.repository.SysRoleRepository;
import com.b511.swpu.cementbackend.repository.Sys_User_RolesRepository;
import com.b511.swpu.cementbackend.service.MetaSystemUnitService;
import com.b511.swpu.cementbackend.service.SysMenuService;
import com.b511.swpu.cementbackend.service.SysRoleService;
import com.b511.swpu.cementbackend.service.SysUserService;
import com.b511.swpu.cementbackend.dto.input.SysUserInputDto;
import com.b511.swpu.cementbackend.exception.CementException;
import com.b511.swpu.cementbackend.repository.SysUserRepository;
import com.b511.swpu.cementbackend.util.*;
import com.b511.swpu.cementbackend.util.enumeration.Code;
import io.swagger.models.auth.In;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;

@Service
public class SysUserServiceImpl implements SysUserService {

    private final static long ONE_DAY_MILLISECOND = 86400000;

    @Value("${server.servlet.context-path}")
    private String contextPath;

    private final SysUserRepository sysUserRepository;

    private final MetaSystemUnitRepository metaSystemUnitRepository;

    private final SysRoleRepository sysRoleRepository;

    private final MetaSystemUnitService metaSystemUnitService;

    private final SysMenuService sysMenuService;

    private final Sys_User_RolesRepository sys_user_rolesRepository;

    private final SysRoleService sysRoleService;

    public SysUserServiceImpl(SysUserRepository sysUserRepository, MetaSystemUnitRepository metaSystemUnitRepository,
                              SysRoleRepository sysRoleRepository, MetaSystemUnitService metaSystemUnitService, SysMenuService sysMenuService, Sys_User_RolesRepository sys_user_rolesRepository, SysRoleService sysRoleService) {
        this.sysUserRepository = sysUserRepository;
        this.metaSystemUnitRepository = metaSystemUnitRepository;
        this.sysRoleRepository = sysRoleRepository;
        this.metaSystemUnitService = metaSystemUnitService;
        this.sysMenuService = sysMenuService;
        this.sys_user_rolesRepository = sys_user_rolesRepository;
        this.sysRoleService = sysRoleService;
    }

    @Override
    // @Transactional
    public R login(SysUserInputDto sysUserInputDto) {
        // TODO: 密码输错三次，固井时间内无法登陆
        // 判断用户是否存在
        SysUser existUser = sysUserRepository.findByUsername(sysUserInputDto.getUsername());
        if (null == existUser){
            // 用户不存在
            throw new CementException(Code.USER_NOT_EXIST);
        }
        // 用户已存在
        // 获取登陆失败的次数
        Integer missNumber = existUser.getMissNumber();
        // 若 missNumber 为 NULL，置为0
        if (null == existUser.getMissNumber()){
            missNumber = 0;
        }

        // 获得该用户上一次登陆的时间
        Long dateLogin = existUser.getMissTime();
        // 获得下次允许登录的时间
        Long dateAllowLogin = existUser.getAllowTime();
        // 获取当前时间
        Long dateNow = System.currentTimeMillis();
        // 日期格式转换
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        // 用户存在
        // 检测 用户名 & 密码 是否输入正确
        SysUser user = sysUserRepository.findByUsernameAndPassword(sysUserInputDto.getUsername(), MD5Util.md5Encrypt32Upper(sysUserInputDto.getPassword()));
        // 密码错误，missNumber += 1
        if (null == user) {
            Integer newMissNumber = missNumber + 1;
            existUser.setMissNumber(newMissNumber);
            sysUserRepository.save(existUser);
            throw new CementException(Code.PSW_ERROR);
        }

        // 密码输入正确
        // 当前时间允许登录
        if( dateAllowLogin == null || dateNow > existUser.getAllowTime() ){
            // 判断错误次数是否达到了3次及以上
            if( missNumber >= 3 ){
                // 2 min 内禁止登录 2 * 60 * 1000 s
                Date allowDate = new Date(System.currentTimeMillis() + 120000);
                Long dateAfterAllowTime = System.currentTimeMillis() + 120000;
                // missNumber 置为 0
                missNumber = 0;
                existUser.setMissNumber(missNumber);
                existUser.setAllowTime(dateAfterAllowTime);
                sysUserRepository.save(existUser);

                // 密码错误3次，在 120 s 后允许登录
                return R.error("密码错误3次及以上，请于" + simpleDateFormat.format(allowDate) + "后重新登录系统！");
            } else {
                // 密码输入正确 且 密码错误次数未达到三次 -> 允许登录
                // missNumber 清零
                missNumber = 0;
                //记录最新登录的时间
                dateLogin = System.currentTimeMillis();
                //记录最新的允许登录时间
                dateAllowLogin = System.currentTimeMillis();
                // 需要保存到数据库
                user.setMissNumber(missNumber);
                user.setMissTime(dateLogin);
                user.setAllowTime(dateAllowLogin);
            }
        } else{
            // 当前时间，不允许登录
            return R.error("当前时间禁止重新登录系统! 请于" + simpleDateFormat.format(user.getAllowTime()) + "后登录！");
        }

        // 用户存在，且通过密码错误次数判断
        if (!user.isEnable()) {
            throw new CementException(Code.ACCOUNT_DISCONTINUED);
        }

        String token = Help.createToken();
        user.setToken(token).setLastLoginTime(System.currentTimeMillis());
        sysUserRepository.save(user);
        //登陆后返回用户的role
        SysUser sysUser = sysUserRepository.findByUsername(sysUserInputDto.getUsername());
        String userId = sysUser.getId();
        String roleID = sys_user_rolesRepository.findRoleID(userId);
        String role= sysRoleRepository.findNameById(roleID);
        // 11.13 update sysUser lock debug
//        if (true){
//            throw new CementException(Code.ACCOUNT_DISCONTINUED);
//        }
        return R.ok()
                .put("token", token)
                .put("menu", sysMenuService.getSysMenuTreeOutputDtoByUser(user))
                .putData(metaSystemUnitService.getMetaSystemUnitValueOutputDto(user))
                .put("navigation",sysRoleService.getAllSysNavigationIDByRoleID(roleID))
                .put("role",role);
    }

    @Override
    @Transactional
    public R loginFromMainSys(SysUserInputDto sysUserInputDto) {
        //主数据跳转登录时，已经MD5加密了，所以这里不用在md5util
        SysUser user = sysUserRepository.findByUsernameAndPassword(sysUserInputDto.getUsername(), sysUserInputDto.getPassword());
        if (null == user) {
            throw new CementException(Code.LOGIN_FAIL);
        }
        if (!user.isEnable()) {
            throw new CementException(Code.ACCOUNT_DISCONTINUED);
        }
        String token = Help.createToken();
        user.setToken(token).setLastLoginTime(System.currentTimeMillis());
        sysUserRepository.save(user);
        //登陆后返回用户的role
        SysUser sysUser = sysUserRepository.findByUsername(sysUserInputDto.getUsername());
        String userId = sysUser.getId();
        String roleID = sys_user_rolesRepository.findRoleID(userId);
        String role= sysRoleRepository.findNameById(roleID);

        return R.ok()
                .put("token", token)
                .put("menu", sysMenuService.getSysMenuTreeOutputDtoByUser(user))
                .putData(metaSystemUnitService.getMetaSystemUnitValueOutputDto(user))
                .put("navigation",sysRoleService.getAllSysNavigationIDByRoleID(roleID))
                .put("role",role);
    }

    @Override
    @Transactional
    public R login2(String data) throws Exception {
        //将url传的值通过“_”分割
        String[] valid=data.split("_");
        //md5为加密前的字符串
        String md5=valid[0]+"_"+valid[1]+"_MDMTOCW";
        //将验证字符串进行MD5加密（大写）
        md5= MD5Util.md5Encrypt32Upper(md5);
        SysUser user =null;
        //将前端传来的MD5和本地的MD5进行验证，如果相同，则登陆
        if(md5.equals(valid[valid.length-1])) {
            //这里是根据主系统传过来的员工编码，查询是否有这个人
            user = sysUserRepository.findByName(valid[0]);
        }
        else{
            user=null;
        }

        if (null == user) {
            throw new CementException(Code.LOGIN_FAIL);
        }
        if (!user.isEnable()) {
            throw new CementException(Code.ACCOUNT_DISCONTINUED);
        }
        String token = Help.createToken();
        user.setToken(token).setLastLoginTime(System.currentTimeMillis());
        String Uname=user.getUsername();
        String psw=user.getPassword();
        //将用户名编码
        Uname= AESUtil.encrypt(Uname);
        psw=AESUtil.encrypt(psw);
        //将特殊字符转码
        Uname=URLEncoder.encode(Uname,"utf-8");
        psw= URLEncoder.encode(psw,"utf-8");
        String Uname_psw=Uname+"_"+psw;
        sysUserRepository.save(user);
        return R.ok()
                .put("username",Uname_psw)
                .put("token",token);
    }

    @Override
    public R getAllUser(PageRequest pageRequest) {
        Page<SysUser> sysUsers = sysUserRepository.findAll(pageRequest);
        sysUsers.getContent().forEach((sysUser -> {
            sysUser.getRoles().forEach(role -> {
                role.setSysMenus(null);
            });
        }));
        return R.ok().putData(new PageOutputDto<>(sysUsers.getTotalElements(), sysUsers.getContent()));
    }

    @Override
    @Transactional
    public R updateUser(String userId, SysUserInputDto sysUserInputDto) {
        Optional<SysUser> optionalSysUser = sysUserRepository.findById(userId);
        if (!optionalSysUser.isPresent()) {
            throw new CementException(Code.SELECT_FAIL);
        }
        SysUser user = optionalSysUser.get();
        CopyUtil.copyProperties(sysUserInputDto, user);
        sysUserRepository.save(user);
        return R.ok();
    }

    @Override
    public boolean userHasRole(String token, String urlPath) {
        SysUser user = sysUserRepository.findByToken(token);
        if (null == user || !user.isEnable()) {
            throw new UsernameNotFoundException(Code.ILLEGAL.getMsg());
        }
        if (null != user.getLastLoginTime() && (System.currentTimeMillis() - user.getLastLoginTime()) > ONE_DAY_MILLISECOND) {
            throw new CredentialsExpiredException(Code.REFRESH.getMsg());
        }
        List<SysRole> roles = user.getRoles();
        for (SysRole role : roles) {
            for (SysMenu menu : role.getSysMenus()) {
                String url = contextPath + menu.getUrl();
                if (urlPath.length() >= url.length() && urlPath.startsWith(url)) {
                    return true;
                }
            }
        }
        throw new BadCredentialsException(Code.INSUFFICIENT_PRIVILEGE.getMsg());
    }

    @Override
    @Transactional
    public R addUser(SysUserInputDto sysUserInputDto) {
        SysUser user = sysUserRepository.findByUsername(sysUserInputDto.getUsername());
        if (null != user) {
            throw new CementException(Code.USER_EXIST);
        }
        SysUser addUser = new SysUser();
        CopyUtil.copyProperties(sysUserInputDto, addUser);

        addUser.setPassword(Help.encryption(sysUserInputDto.getPassword()));
        // 遍历List取Role
        // 批量ID查询,findAllById(Iterable ids);
        List<SysRole> sysRoleList = sysRoleRepository.findAllById(sysUserInputDto.getRoles());
        addUser.setRoles(sysRoleList);
        // 默认设定国际单位
        // TODO fix bug
        // 默认设定为当前系统的第一个单位
        MetaSystemUnit defaultUnit = metaSystemUnitRepository.findFirstByName("国际单位");
        addUser.setMetaSystemUnit(defaultUnit);

        sysUserRepository.save(addUser);
        return R.ok();
    }

    @Override
    @Transactional
    public R deleteUser(String id) {
        sysUserRepository.deleteById(id);
        return R.ok();
    }

    @Override
    public R getUserById(String id) {
        SysUserOutputDto result = new SysUserOutputDto();
        Optional<SysUser> user = sysUserRepository.findById(id);
        Set<SysRole> role = new HashSet(user.get().getRoles());
        result.setRoles(role);
        BeanUtils.copyProperties(user.get(), result);
        return R.ok().putData(result);
    }

    @Override
    public R getUname(HttpServletRequest request) {
        String token=request.getHeader("Authorization");
        SysUser user = sysUserRepository.findByToken(token);
        return R.ok().put("username",user.getUsername());
    }

}
